package edu.mse.camel.client.uml.collab.emflistener;

import java.util.Collection;
import java.util.ListIterator;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.change.ChangeKind;
import org.eclipse.emf.ecore.change.FeatureChange;
import org.eclipse.emf.ecore.change.ListChange;
import org.eclipse.emf.ecore.change.impl.FeatureChangeImpl;
import org.eclipse.emf.ecore.change.impl.ListChangeImpl;
import org.eclipse.emf.ecore.change.util.ChangeRecorder;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;

import edu.mse.camel.client.log.Debug;
import edu.mse.camel.client.uml.collab.CollabPlugin;

public class UMLChangeRecorder extends ChangeRecorder {

	private static class UniqueListChangeImpl
	extends ListChangeImpl {

		@Override
		public void apply(EList<Object> originalList) {

			switch (getKind().getValue()) {
			case ChangeKind.ADD :

				if (index == -1) {

					for (Object value : getValues()) {

						if (!originalList.contains(value)) {
							originalList.add(value);
						}
					}
				} else {
					EList<Object> values = getValues();

					for (ListIterator<Object> v = values
							.listIterator(values.size()); v.hasPrevious();) {

						Object value = v.previous();

						if (!originalList.contains(value)) {
							originalList.add(index, value);
						}
					}
				}

				break;
			case ChangeKind.REMOVE :

				for (Object value : getValues()) {
					originalList.remove(value);
				}

				break;
			default :
				super.apply(originalList);
			}
		}

		@Override
		public void applyAndReverse(EList<Object> originalList) {
			switch (getKind().getValue()) {
			case ChangeKind.ADD :

				if (index == -1) {
					index = originalList.size();

					for (Object value : getValues()) {

						if (!originalList.contains(value)) {
							originalList.add(value);
						}
					}
				} else {
					EList<Object> values = getValues();

					for (ListIterator<Object> v = values
							.listIterator(values.size()); v.hasPrevious();) {

						Object value = v.previous();

						if (!originalList.contains(value)) {
							originalList.add(index, value);
						}
					}
				}

				setKind(ChangeKind.REMOVE_LITERAL);

				break;
			case ChangeKind.REMOVE :

				for (Object value : getValues()) {
					originalList.remove(value);
				}

				setKind(ChangeKind.ADD_LITERAL);

				break;
			default :
				super.applyAndReverse(originalList);
			}
		}

	}

	private static final class UniqueFeatureChangeImpl
	extends FeatureChangeImpl {

		protected UniqueFeatureChangeImpl(EStructuralFeature feature,
				Object value, boolean isSet) {
			super(feature, value, isSet);
		}

		@Override
		protected ListChange createListChange(EList<ListChange> changesList,
				ChangeKind kind, int index) {

			if (getFeature().isUnique()) {
				ListChange listChange = new UniqueListChangeImpl();

				listChange.setKind(kind);
				listChange.setIndex(index);

				changesList.add(listChange);

				return listChange;
			} else {
				return super.createListChange(changesList, kind, index);
			}
		}

	}
	
	private boolean featureIsUnique = false;

	@Override
	protected ListChange createListChange(
			EList<ListChange> changesList, ChangeKind kind, int index) {

		if (featureIsUnique) {
			ListChange listChange = new UniqueListChangeImpl();

			listChange.setKind(kind);
			listChange.setIndex(index);

			changesList.add(listChange);

			return listChange;
		} else {
			return super.createListChange(changesList, kind, index);
		}
	}

	@Override
	protected void createRemoveListChange(EList<?> oldList,
			EList<ListChange> changesList, Object newObject, int index) {

		if (featureIsUnique) {
			ListChange listChange = createListChange(changesList,
				ChangeKind.REMOVE_LITERAL, index);
			listChange.getValues().add(oldList.remove(index));
		} else {
			super.createRemoveListChange(oldList, changesList,
				newObject, index);
		}
	}

	@Override
	protected void finalizeChange(FeatureChange change, EObject eObject) {
		featureIsUnique = change.getFeature().isUnique();

		super.finalizeChange(change, eObject);
	}

	@Override
	protected FeatureChange createFeatureChange(EObject eObject,
			EStructuralFeature eStructuralFeature, Object value,
			boolean isSet) {

		return new UniqueFeatureChangeImpl(eStructuralFeature, value,
			isSet);
	}

	
	@Override
	protected boolean shouldRecord(EStructuralFeature feature,
			EReference containment, Notification notification, EObject object) {
/*
		if (feature instanceof EReference) {
			if (! ((EReference) feature).isContainment()) {
				Debug.log_info(CollabPlugin.PLUGIN_ID, "shouldRecord: false on " + feature);
				return false;
			}
		}
*/
		return super.shouldRecord(feature, containment, notification, object);
	}
	
	private boolean shouldPassDown (Notification notification) {
		Object notifier = notification.getNotifier();
	    if (notifier instanceof EObject)
	    {
	      Object feature = notification.getFeature();
	      if (feature instanceof EReference)
	      {
	      }
	      else if (feature != null)
	      {
	      }
	      else {
	    	  // ignored by the change recorder !
	    	  return false;
	      }
	    }
	    else if (notifier instanceof Resource)
	    {
	      int featureID = notification.getFeatureID(Resource.class);
	      switch (featureID) 
	      {
	        case Resource.RESOURCE__CONTENTS:
	        {
	          break;
	        }
	        case Resource.RESOURCE__IS_LOADED:
	        {
	          break;
	        }
	        default:
	        {
	        	// ignored by the change recorder !
	        	return false;
	        }
	      }
	    }      
	    else if (notifier instanceof ResourceSet)
	    {
	      if (notification.getFeatureID(ResourceSet.class) == ResourceSet.RESOURCE_SET__RESOURCES)
	      {
	        switch (notification.getEventType())
	        {
	          case Notification.ADD:
	          case Notification.SET:
	          //case Notification.REMOVE:
	          {
	        	  // not ignored by the change recorder, but we do not care about resource change at all
	        	  return false;
	          }
	          
	          case Notification.ADD_MANY:
	          //case Notification.REMOVE_MANY:
	          {
	        	// not ignored by the change recorder, but we do not care about resource change at all
	        	  return false;
	          }
	          default:
	          {
		      // ignored by the change recorder !
	        	  return false;
		      }
	        }
	      }
	      else {
	    	// ignored by the change recorder !
	    	  return false;
	      }
	    }
	    else {
	    	// ignored by the change recorder !
	    	return false;
	    }
	    return true;
	}

	@Override
	public void notifyChanged(Notification notification) {
		
		/*
		if(!shouldPassDown(notification)) {
			return;
		}
		Debug.log_info(CollabPlugin.PLUGIN_ID, "notifyChanged: received " + notification);
		
		EStructuralFeature feature = (EStructuralFeature) notification.getFeature();
		if(feature != null) {
			Debug.log_info(CollabPlugin.PLUGIN_ID, "  feature: " + feature);
			if (feature instanceof EReference) {
				if (! ((EReference) feature).isContainment()) {
					// for associations in class diagram (and something else in other places)
					// the change recorder spits out unnecessary, incomplete information
					// We do not want the change recorder to see this at all
					Debug.log_info(CollabPlugin.PLUGIN_ID, "------ non containment reference -------");
					//return;
				}
			} 
		}
		else {

				Debug.log_info(CollabPlugin.PLUGIN_ID, "notifyChanged: ======= special ========");
		}
		Debug.log_info(CollabPlugin.PLUGIN_ID, "");
		*/
		
		super.notifyChanged(notification);
	}


}
